SPB Git

Repository Public

page.tsx 6.8 KB 107 lines tsx

1import Link from "next/link";2import { FilmCard } from "@/components/FilmCard";3import { HeroPlayer } from "@/components/HeroPlayer";4import { catalogStats, heroFilm, listCollections, listFilms, listSeries } from "@/lib/films";5import { dict, fmtDuration, href, type Locale } from "@/lib/i18n";67export default async function Home({ params }: { params: Promise<{ locale: string }> }) {8  const locale = (await params).locale as Locale;9  const t = dict(locale);10  const [hero, latest, top, series, collections, stats] = await Promise.all([11    heroFilm(locale), listFilms(locale, { limit: 10 }), listFilms(locale, { limit: 5, sort: "top" }), listSeries(locale), listCollections(locale), catalogStats(),12  ]);1314  return (15    <>16      {/* First viewport: an actual playable film, or an honest empty state. */}17      <section className="grid items-center gap-10 py-8 md:grid-cols-[minmax(0,1fr)_auto] md:gap-16 md:py-14">18        <div className="rise order-2 md:order-1">19          <p className="kicker">{hero ? t.home.kicker : t.home.soon}</p>20          {hero ? (21            <>22              {hero.series && <p className="accent-text mt-6 text-sm font-semibold uppercase tracking-[0.16em]" style={{ "--a": hero.series.accent } as React.CSSProperties}>{hero.series.title}{hero.episode ? ` · ${hero.episode}` : ""}</p>}23              <h1 className="display mt-3 text-[clamp(3rem,8vw,6.5rem)]">{hero.title}</h1>24              <p className="prose-film mt-6 text-lg">{hero.synopsis}</p>25              <div className="mt-8 flex flex-wrap items-center gap-3">26                <Link href={href(locale, `/film/${hero.slug}`)} className="btn btn-primary">{t.home.watch}</Link>27                <Link href={href(locale, "/feed")} className="btn btn-ghost">{t.home.openFeed}</Link>28                <span className="ml-1 text-sm tabular-nums text-[var(--color-faint)]">{fmtDuration(hero.duration)} · {t.film.aiDisclosure}</span>29              </div>30            </>31          ) : (32            <>33              <h1 className="display mt-6 text-[clamp(3rem,8vw,6rem)]">{t.home.emptyTitle}</h1>34              <p className="prose-film mt-6 text-lg">{t.home.emptyBody}</p>35              <div className="mt-8"><Link href={href(locale, "/about")} className="btn btn-ghost">{t.home.processLink}</Link></div>36            </>37          )}38        </div>39        <div className="order-1 mx-auto w-full max-w-[440px] md:order-2 md:w-[min(34vw,400px)]">40          <div className="frame relative aspect-[9/16]">41            {hero ? <HeroPlayer film={hero} labels={t.player} locale={locale} /> : (42              <div className="grid h-full place-items-center bg-[radial-gradient(circle_at_50%_40%,#2a2418,transparent_60%)]">43                <div className="h-3 w-3 rounded-full bg-[var(--color-lamp)] shadow-[0_0_60px_18px_rgba(232,194,122,0.35)]" />44              </div>45            )}46          </div>47        </div>48      </section>4950      {latest.films.length > 0 && (51        <section className="mt-14" aria-labelledby="latest">52          <div className="mb-5 flex items-end justify-between gap-4">53            <div><h2 id="latest" className="display text-3xl md:text-4xl">{t.home.latest}</h2><p className="mt-1 text-sm text-[var(--color-faint)]">{t.home.latestSub}</p></div>54            <Link href={href(locale, "/explore")} className="btn btn-ghost btn-sm">{t.nav.explore}</Link>55          </div>56          <div className="rail -mx-4 px-4 sm:-mx-6 sm:px-6">57            {latest.films.map((f, i) => <div key={f.id} className="w-[46vw] sm:w-[220px]"><FilmCard film={f} locale={locale} priority={i < 5} /></div>)}58          </div>59        </section>60      )}6162      {collections.map((c) => (63        <section key={c.slug} className="mt-16" aria-label={c.title}>64          <div className="mb-5"><p className="label">Collection</p><h2 className="display mt-1 text-4xl">{c.title}</h2><p className="mt-1 text-sm text-[var(--color-faint)]">{c.description}</p></div>65          <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 sm:gap-4 lg:grid-cols-5">{c.films.slice(0, 5).map((f) => <FilmCard key={f.id} film={f} locale={locale} />)}</div>66        </section>67      ))}6869      <section className="mt-16" aria-labelledby="series">70        <div className="mb-5"><h2 id="series" className="display text-3xl md:text-4xl">{t.home.series}</h2><p className="mt-1 text-sm text-[var(--color-faint)]">{t.home.seriesSub}</p></div>71        <div className="grid gap-px overflow-hidden rounded-[22px] border border-[var(--color-line-soft)] bg-[var(--color-line-soft)] sm:grid-cols-2 lg:grid-cols-4">72          {series.map((s) => (73            <Link key={s.slug} href={href(locale, `/series/${s.slug}`)} className="group relative flex min-h-[190px] flex-col justify-between bg-[var(--color-stage)] p-5 transition-colors hover:bg-[var(--color-panel)]">74              <div className="flex items-center justify-between">75                <span className="h-2 w-2 rounded-full" style={{ background: s.accent }} />76                <span className="text-xs tabular-nums text-[var(--color-faint)]">{s.count > 0 ? `${s.count} ${t.seriesPage.episodes}` : t.seriesPage.upcoming}</span>77              </div>78              <div><h3 className="display text-2xl">{s.title}</h3><p className="mt-2 text-sm leading-relaxed text-[var(--color-muted)]">{s.description}</p></div>79            </Link>80          ))}81        </div>82      </section>8384      {top.films.length > 2 && (85        <section className="mt-16" aria-labelledby="top">86          <div className="mb-5"><h2 id="top" className="display text-3xl md:text-4xl">{t.home.top}</h2><p className="mt-1 text-sm text-[var(--color-faint)]">{t.home.topSub}</p></div>87          <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 sm:gap-4 lg:grid-cols-5">{top.films.map((f) => <FilmCard key={f.id} film={f} locale={locale} note={f.qualityScore ? `${f.qualityScore.toFixed(1)} / 10` : undefined} />)}</div>88        </section>89      )}9091      <section className="mt-20 grid gap-10 border-t border-[var(--color-line-soft)] pt-12 md:grid-cols-[1.2fr_1fr]" aria-labelledby="process">92        <div>93          <p className="kicker">{t.home.process}</p>94          <h2 id="process" className="display mt-4 text-4xl md:text-5xl">{t.about.title}</h2>95          <p className="prose-film mt-5 text-lg">{t.home.processBody}</p>96          <Link href={href(locale, "/about")} className="btn btn-ghost mt-7">{t.home.processLink}</Link>97        </div>98        <dl className="grid grid-cols-3 content-center gap-4">99          {([[stats.films, t.home.stats.films], [(stats.seconds / 60).toFixed(1), t.home.stats.minutes], [stats.shots, t.home.stats.shots]] as const).map(([n, label]) => (100            <div key={label} className="border-l border-[var(--color-line)] pl-4"><dd className="display text-5xl tabular-nums">{n}</dd><dt className="mt-2 text-xs uppercase tracking-[0.14em] text-[var(--color-faint)]">{label}</dt></div>101          ))}102        </dl>103      </section>104    </>105  );106}107
Files main
Menu

Theme